#ifndef _GEN_STRUCT_HPP_
#define _GEN_STRUCT_HPP_
#include <iostream>
#include <vector>
/*GenStruct is a singleton class - contains general information about the process:
   -count of threads, etc. array pointer
   -graphics window descriptor
   -accuracy parameters eps,G,T,max_coord
   -time step(via double)
   -number of particles
   -tree root pointer
   -particle list(vector)
*/
#define TREE_DIMENSION 8
typedef struct {
	double eps,g,T;
} accuracy_struct;

class GenStruct {
	public:
		double eps, g, T, max_coord;	//current accuracy(vect.length), gr_constant, theta accuracy
		int dTime;
		TNode *treeRoot;
		std::vector<Particle> particle_vect;

		//static getter, must perform @initGStructInstance(...) before using it
		static GenStruct getGStructInstance() {
			return *instance;
		}
		static void initGStructInstance(int thr_count, int wd, int particles_count, 
			accuracy_struct str, double m_coord) {
			if (instance == NULL) {
				instance = new GenStruct(thr_count, wd, particles_count, accuracy_struct str, max_coord); 
			} else {
				std::cerr << "Can't init GenStruct class. It's already created" << std::endl;
			}
		}
		~GenStruct() {
			delete[] p_thr;
			delete instance;
		};
	private:
		//TODO
		//implement considering eps,g,T,max_coord
		GenStruct(int thr_count, int wd, int particles_count, 
			accuracy_struct str, double m_coord) {
			n_thr = thr_count;
			p_thr = new pthread_t[thr_count];
			glwd = wd;
			n_particles = particles_count;
			eps = str.eps; g = str.g; T = str.T;
			max_coord = m_coord;
			tree_root = new TNode(m_coord); 
		};
		static GenStruct* instance = NULL;
		int n_thr, glwd;	//threads number, GL. window descriptor
		pthread_t *p_thr;
		int n_particles;	//time to redraw(glut timer), particles value	
		
		TNode *tree_root;
};
/*
type 	 x 	 y 	 z
 1		 1	-1	-1
 2		-1	-1	-1
 3		-1	 1	-1
 4		 1	 1	-1
 5		 1	-1	 1
 6	    -1	-1	 1
 7		-1	 1	 1
 8 		 1	 1	 1
*/
 /*
 	Class intended for implementing Barnes-Hutt tree algorithm
 	Each tree node is eqaul predefined area. Each leaf - contains only 1 or 0 particles
	 	@TNode() - constructor for tree root, args - initial all over space size
	 	@TNode(TNode *up, int type) - constructor for specified node type of an octree
	 	@count_mass_distribution - updates center_mass vector(field of the class)
	 	@insert_particle - inserts particle to *this node, performs recursive insert
	 	@is_inside_node - checks whether coordinates apply the corresponding area of the node
	 	..
	 	..
	 private:
	 	@init_node - initalises all fields of the class, that's all, conects @this instance to the tree

 */
class TNode {
	public:
		TNode(double size) {
			mass = 0;
			level = 0;
			n_particles = 0;
			body_id = -1;
			e_length = size;
			for (int i = 0; i < TREE_DIMENSION; ++i) {
				children[i] = NULL;
			}
			ld_vertex = Vec3((-1)*size/2, (-1)*size/2, (-1)*size/2);
			ru_vertex = Vec3(size/2, size/2, size/2);
		}
		TNode(TNode *up_node, int type) {
			init_node(up_node, type);
		};
		void count_mass_distribution(const GenStruct& gen_struct) {
			if (n_particles == 0) {
				mass_center = 0;
			}
			if (n_particles == 1) {
				mass_center = gen_struct.particle_vect[body_id].mass;
			}
			if (n_particles > 1) {
				for (int i = 0; i < TREE_DIMENSION; ++i) {
					children[i].count_mass_distribution(gen_struct);
					mass_center += (children[i].mass_center * children[i].mass);
				}
				mass_center /= mass;
			}
		};
		void insert_particle(const Particle& particle) {
			if (n_particles == 0) {
				body_id = particle.id;
				//init children, for future insertings
				for (int i = 0; i < TREE_DIMENSION; ++i) {
					init_node(*this, i);
				}
			} else {
				//if particles > 0, then thera must be initialized children
				int type;
				for (int i = 0; i < TREE_DIMENSION; ++i) {
					if (*this.children[i].is_inside_node(particle)) {
						type = i;
						break;
					}
				}
				*this.children[type].insert_particle(particle); 
			}
			mass += particle.mass;
			n_particles += 1;
		};
		bool is_inside_node(const Particle& particle) {
			return ((particle.coord > ld_vertex) && (particle.coord < ru_vertex));
		};
		inline double get_mass() { return mass; }
		inline Vec3 get_mass_center() {	return mass_center;	} 
	private:
		void init_node(TNode& up_node, int type) {
			up_node.children[type] = this;
			*this.parent = &up_node;
			n_particles = 0;
			body_id = -1;
			mass = 0;
			level = up_node.level + 1;
			e_length = up_node.edge_length/2

			//magic constants - 0..7
			switch (type % TREE_DIMENSION) {
					case 0: ld_vertex = up_node.ld_vertex;
							break;
					case 1: ld_vertex = up_node.ld_vertex + Vec3(0, e_length, 0);
							break;
					case 2: ld_vertex = up_node.ld_vertex + Vec3(e_length, e_length, 0);
							break;
					case 3: ld_vertex = up_node.ld_vertex + Vec3(e_length, 0, 0);
							break;
					case 4: ld_vertex = up_node.ld_vertex + Vec3(0, 0, e_length);
							break;
					case 5: ld_vertex = up_node.ld_vertex + Vec3(0, e_length, e_length);
							break;
					case 6: ld_vertex = up_node.ld_vertex + Vec3(e_length, e_length, e_length);
							break;
					case 7: ld_vertex = up_node.ld_vertex + Vec3(e_length, 0, e_length);
							break;

					default: std::cerr << "Should never got here!" << std::endl;
							 break;
			}
			ru_vertex = ld_vertex + (up_node.ru_vertex - up_node.ld_vertex)/2;
			for (int i = 0; i < TREE_DIMENSION; ++i) {
				children[i] = NULL;
			}
		}
        int body_id;       //each leaf - one particle, -1 eqauls not being a list
        int n_particles;
        
        TNode *children[8];	//octree pointers
        TNode *parent;
    
        int level;      		  //level of recursion
        double mass, e_length;    //summary mass of all points inside, edge length
        Vec3  mass_center, ld_vertex, ru_vertex;
};
/* class contains operators @+,@+=,@-,@-=,@*,
	@*= double multiplication
	@^  scalar multiplication
	@len() - vec3 magnitude
	@= assign value
	general 3d vector class, intended to simplify all linear algebra operations
*/
class Vec3 {
	public:
		double x,y,z;
		Vec3() : x(0), y(0), z(0) {};
		Vec3(double _x, double _y, double _z) {
			x = _x;
			y = _y;
			z = _z;
		}
		Vec3& operator=(const double rhs_value) {
			x = rhs_value; y = rhs_value; z = rhs_value;
		}
		Vec3(const Vec3& vect) {
			x = vect.x; y = vect.y; z = vect.z;
			return *this;
		}
		Vec3& operator=(Vec3 rhs) {
			std::swap(*this, rhs);
			return *this;
		}

		Vec3& operator+=(const Vec3& rhs) {
			x += rhs.x;
			y += rhs.y;
			z += rhs.z;
			return *this;
		}
		inline Vec3 operator+(Vec3 lhs, const Vec3& rhs) {
			lhs += rhs;
			return lhs;
		}

		Vec3& operator*=(const double& rhs) {
			*this.x *= rhs;
			*this.y *= rhs;
			*this.z *= rhs;
			return *this;
		}
		inline Vec3 operator*(Vec3 lhs, const double& rhs) {
			lhs *= rhs;
			return lhs;
		}
		Vec3& operator/=(const double& rhs) {
			if (rhs != 0) {
				*this *= (1/rhs);
				return *this;
			} else {
				//TODO - create logger
				//write exception log to log file
			}
		}
		inline Vec3 operator/(const Vec3 lhs, const double& rhs) {
			lhs /= rhs;
			return lhs;
		}
		Vec3& operator-=(const Vec3& rhs) {
			*this += ((-1)*rhs);
		}
		inline Vec3 operator-(Vec3 lhs, const Vec3& rhs) {
			lhs -= rhs;
			return lhs;
		}
		double operator^(const Vec3& lhs, const Vec3& rhs) {
			return (lhs.x*rhs.x + lhs.y*rhs.y + lhs.z*rhs.z);
		}
		bool operator>(const Vec3& lhs, const Vec3& rhs) {
			return ((lhs.x > rhs.x) && (lhs.y > rhs.y) && (lhs.z > rhs.z));
		}
		bool operator<(const Vec3& lhs, const Vec3& rhs) {
			return ((lhs.x < rhs.x) && (lhs.y < rhs.y) && (lhs.z < rhs.z));
		}

		double len() { return sqrt((*this)^(*this)); };
};
//TODO
//implement Particle class, only simplest methods
//no further logic should be implemented, only simple interactions and fields
class Particle {
	public:
		Vec3 coord, vel, gr_force;
		double mass;
		int id;

		Particle() {};
		void count_force(const &Particle particle) {};
		~Particle() {};
	private:
};

//this struct is used to aggregate the general data of running program
typedef struct {
	int first, last; //particle range of calculating
} thr_arg;
#endif
